import copy
from regular_matching_rules import get_keywordTredTree_instance
from spidertools.utils.snippets import combine_two_dict
from info_fsm import InfoMachine
import numpy as np
import itertools
from utils.table_utils import get_tr_parsed_value, get_first_trs


class tableModelEnum():
    key_value_mode = "key_value_mode"
    key_line_mode = "key_line_mode"
    key_row_mode = "key_row_mode"
    key_row_parsemode_mode = "key_row_parsemode_mode"
    one_item_mode = "one_item_mode"
    unknow_table_mode = "unknow_table_mode"
    end_table_mode = "end_table_mode"


class BaseTableState():
    def __init__(self, mode, extend_keywords=None, row_shifting=0):
        self.mode = mode
        self.row_shifting = row_shifting
        self.triedTree = get_keywordTredTree_instance()

        if extend_keywords:
            self.triedTree = copy.deepcopy(self.triedTree)
            self.triedTree.update_triedTree_keys(extend_keywords)

        self.text_machine = InfoMachine(base_regex='%s:\s*(.*)',extend_keywords=extend_keywords)

        self.td_nums = 0
        self.result = {}

    def reset_state(self):
        '''重置State'''
        self.result = {}
        self.td_nums = 0

    def output_result(self):
        self.reset_state()
        return {}

    def get_single_tr_mode(self, td_types, td_values):
        '''
            获取单行tr可能触发的一些mode
        '''
        table_mode = tableModelEnum.unknow_table_mode

        total_tds_len = len(td_types)
        table_key_count = td_types.count('table_key')

        if total_tds_len == 0:
            return table_mode

        if total_tds_len == 1:
            table_mode = tableModelEnum.one_item_mode
        elif table_key_count == round(len(td_types) / 2) and td_types[0] == "table_key":
            table_mode = tableModelEnum.key_value_mode
        elif td_types[0] == 'table_key' and table_key_count == 1:
            table_mode = tableModelEnum.key_row_mode
        elif td_types[0] != "table_key" and table_key_count == total_tds_len - 1:
            table_mode = tableModelEnum.key_row_parsemode_mode
        elif td_values[-1] == "" and table_key_count == total_tds_len - 1:
            # 允许table_key的最后一位是空字符串
            table_mode = tableModelEnum.key_line_mode
        elif table_key_count == len(td_types):
            table_mode = tableModelEnum.key_line_mode
        elif self.check_key_line(td_types, td_values):
            table_mode = tableModelEnum.key_line_mode
        return table_mode

    def check_key_line(self, table_key_lists, td_values_lists):
        one_line_mode_flag = False
        consecutive = 0
        for x, y in itertools.groupby(table_key_lists):
            if not x:
                continue
            length = len(list(y))
            if length > 1:  # ['table_key', "", 'table_key', 'table_key', '', 'table_key','table_key']
                consecutive += 1
                if consecutive > 1:
                    one_line_mode_flag = True
            if length > 2:  # ['table_key' 'table_key', 'table_key', '', 'table_key']
                one_line_mode_flag = True
        # if one_line_mode_flag:
        #     with open("check_key_line.log", 'a+', encoding="utf-8") as f:
        #         table_key_lists_new = []
        #         for index, i in enumerate(table_key_lists):
        #             if i:
        #                 table_key_lists_new.append(str(index+1) + "." + i)
        #             else:
        #                 table_key_lists_new.append(f"{index+1}.####")
        #         td_values_lists = [str(index+1) + "." + i for index, i in enumerate(td_values_lists)]
        #         msg = "table_key_lists:" + "\t".join(table_key_lists_new) + "\ntd_values_lists:" + "\t".join(
        #             td_values_lists) + "\n\n"
        #         f.write(msg)
        return one_line_mode_flag

    def exec(self, machine, tr):
        '''
            根据传入的item信息跟新当前State里面的dict
            :param item:
            :return:
        '''
        td_types, td_values = machine.parsed_td_values_and_types(tr)
        td_types, td_values = td_types[self.row_shifting:], td_values[self.row_shifting:]

        parsed_table_mode = self.get_single_tr_mode(td_types, td_values)
        result = machine.update_state(parsed_table_mode, td_types)
        machine.current_state.update_mode_info(td_values)

        return result

    def check_tds_ok(self, td_types):
        return False

    def update_mode_info(self, td_values):
        pass  # do nothing

    def check_if_need_reset(self, new_status, td_types):
        '''根据当前的status,判断新来的tr_status是否需要重置列表状态'''
        if new_status != self.mode or not self.check_tds_ok(td_types):
            return True
        else:
            return False

    def update_value_parse_mode(self, parse_mode_list):
        pass


class KeyValueMode(BaseTableState):
    '''
    Key:value
    '''

    def __init__(self,extend_keywords=None):
        super(KeyValueMode, self).__init__(mode=tableModelEnum.key_value_mode,extend_keywords=extend_keywords)
        self.bidder_info = {}

    def reset_state(self):
        super().reset_state()
        self.bidder_info = {}

    def check_tds_ok(self, td_types):
        '''
        key:value这种形式，直接返回True就行
        '''
        return True

    def update_mode_info(self, td_values):
        '''
        将当前tds下面的内容跟新到mode里面区
        '''
        if 'values' not in self.result:
            self.result['values'] = []

        for index in range(int(len(td_values) / 2)):
            # 临时将冒号替换###，经过文本状态机解析以后在还原为冒号
            td_values[index * 2 + 1] = td_values[index * 2 + 1].replace(":", "###")
            self.result['values'].append(td_values[index * 2] + ":" + td_values[index * 2 + 1])

    def output_result(self):
        # 执行状态机，解析整个文本

        _result = []
        if 'value_parse_mode' in self.result:
            _result.append(self.result['value_parse_mode'] + ":")

        _result.extend(self.result['values'])
        output_dict = self.text_machine.run_list(_result)

        # 只遍历第二层的key,value，并替换###为：
        for key,value in output_dict.items():
            if type(value) == dict:
                for sub_key,sub_value in value.items():
                    if type(sub_value)  == str:
                        value[sub_key] = sub_value.replace("###",":")
                    elif type(sub_value) == list:
                        new_sub_value = []
                        for v in sub_value:
                            new_sub_value.append(v.replace("###",":"))
                        value[sub_key] = new_sub_value
            elif type(value) == list and value and type(value[0]) == dict:
                for item in value :
                    for sub_key,sub_value in item.items():
                        item[sub_key] = sub_value.replace("###",":")

        self.reset_state()
        return output_dict

    def update_value_parse_mode(self, result):
        if 'value_parse_mode' in result:
            self.result['value_parse_mode'] = result['value_parse_mode']
            del result['value_parse_mode']


class KeyLineMode(BaseTableState):
    '''
    通用表格类型2转换类，

    key1 key2 key3 key4
    value1 value2 value3 value4

    '''

    def __init__(self,extend_keywords=None):
        super(KeyLineMode, self).__init__(mode=tableModelEnum.key_line_mode,extend_keywords=extend_keywords)
        self.find_head_keys = False

    def reset_state(self):
        super().reset_state()
        self.find_head_keys = False

    def update_mode_info(self, td_values):
        '''
        将当前tds下面的内容跟新到mode里面区
        '''
        if not self.find_head_keys:
            self.find_head_keys = True
            self.result["keys"] = td_values
            self.td_nums = len(td_values)
        else:
            if 'values' not in self.result:
                self.result['values'] = []
            self.result['values'].append(td_values)

    def check_tds_ok(self, td_types):
        '''
        先判断是不是 key line
        如果是已经找到了key line，那么接下来的应该value line
        '''
        check_ok = False
        current_td_nums = len(td_types)
        table_key_count = td_types.count('table_key')
        if self.td_nums == 0:
            if table_key_count == current_td_nums:
                check_ok = True
        else:
            if self.td_nums == current_td_nums:
                if table_key_count == 0:
                    check_ok = True
                elif table_key_count <= int(self.td_nums / 4):
                    if td_types[0] != "table_key":
                        check_ok = True
        return check_ok

    def output_result(self):
        # 执行状态机，解析整个文本
        result = {}
        if 'values' in self.result:
            result_keys = self.result['keys']
            result_values = self.result['values']

            for item in result_values:
                tmp_result = []
                for key, value in zip(result_keys, item):
                    tmp_result.append("%s:%s" % (key, value))
                    # tmp_result[key] = value


                # 执行状态机，解析整个文本
                output_dict = self.text_machine.run_list(tmp_result)
                result = combine_two_dict(result, output_dict)
        self.reset_state()
        return result

    def check_if_need_reset(self, new_status, td_types):
        '''根据当前的status,判断新来的tr_status是否需要重置列表状态'''
        reset = True
        if self.check_tds_ok(td_types):
            reset = False
        return reset


class KeyRowParseModeMode(BaseTableState):
    '''
       作为keyRowMode的前置状态，在某些情况下KeyRowMode每一列的归属状态
    '''

    def __init__(self,extend_keywords=None):
        super(KeyRowParseModeMode, self).__init__(mode=tableModelEnum.key_row_parsemode_mode,extend_keywords=extend_keywords)

    def check_tds_ok(self, td_types):
        return True

    def output_result(self):
        result = self.result['rows_status']
        self.reset_state()
        return result

    def update_mode_info(self, td_values):
        '''
        将当前tds下面的内容跟新到mode里面区
        '''
        self.result['rows_status'] = td_values[1:]


class KeyRowMode(BaseTableState):
    '''
        key1 value1 value2 。。。
        key2 value1 value2 。。。
        key3 value1 value2 。。。
    '''

    def __init__(self,extend_keywords=None):
        super(KeyRowMode, self).__init__(mode=tableModelEnum.key_row_mode,extend_keywords=extend_keywords)

    def update_value_parse_mode(self, parse_mode_list):
        if 'value_parse_mode' not in self.result:
            self.result["value_parse_mode"] = []
        self.result["value_parse_mode"] = parse_mode_list

    def update_mode_info(self, td_values):
        '''
        将当前tds下面的内容跟新到mode里面区
        '''
        if self.td_nums == 0:
            self.td_nums = len(td_values)

        if self.td_nums == len(td_values):
            if 'keys' not in self.result:
                self.result["keys"] = []
            if "values" not in self.result:
                self.result['values'] = []

            self.result['keys'].append(td_values[0])
            self.result['values'].append(td_values[1:])

    def check_tds_ok(self, td_types):
        '''
        每一行判断
        如果KeyRowMode，第一个td是 key,后面的tds都是 value
        '''
        check_ok = False
        # 第一次进来，td_nums等于0，直接记录td_nums,然后返回True
        current_td_nums = len(td_types)
        if self.td_nums == 0:
            check_ok = True
        else:
            if current_td_nums == self.td_nums:
                if td_types[0] == "table_key":
                    check_ok = True
        return check_ok

    def check_if_need_reset(self, new_status, td_types):
        '''根据当前的status,判断新来的tr_status是否需要重置列表状态'''
        reset = True
        if new_status == self.mode and self.check_tds_ok(td_types):
            reset = False
        return reset

    def output_result(self):
        # 执行状态机，解析整个文本
        result = {}
        if 'values' in self.result:
            result_keys = self.result['keys']
            result_values = self.result['values']
            result_values = np.array(result_values, dtype=object).transpose().tolist()
            has_parse_mode = False
            if "value_parse_mode" in self.result and len(result_values) == len(self.result["value_parse_mode"]):
                has_parse_mode = True

            for index, item in enumerate(result_values):
                tmp_result = []
                if has_parse_mode:
                    tmp_result.append(self.result['value_parse_mode'][index] + ":")

                for key, value in zip(result_keys, item):
                    tmp_result.append("%s:%s" % (key, value))

                # 执行状态机，解析整个文本
                output_dict = self.text_machine.run_list(tmp_result)
                result = combine_two_dict(result, output_dict)

        self.reset_state()
        return result


class OneItemMode(BaseTableState):
    '''
    table 一行只有一个袁术
    '''

    def __init__(self, return_result=True,extend_keywords=None):
        super(OneItemMode, self).__init__(mode=tableModelEnum.one_item_mode,extend_keywords=extend_keywords)
        self.return_result = return_result

    def update_mode_info(self, td_values):
        if 'texts' not in self.result:
            self.result['texts'] = []
        self.result['texts'].extend(td_values)

    def output_result(self):
        # 执行状态机，解析整个文本
        output_dict = {}
        if self.return_result:
            output_dict = self.text_machine.run_list(self.result['texts'])

            text_str = "".join(self.result["texts"])
            text_parsed_mode = self.text_machine.get_single_text_parsedmode(text_str + ":")
            if text_parsed_mode:
                output_dict["value_parse_mode"] = text_parsed_mode

            self.reset_state()
        return output_dict


class UnknowTableMode(BaseTableState):
    '''
    table 一行只有一个td
    '''

    def __init__(self,extend_keywords=None):
        super(UnknowTableMode, self).__init__(mode=tableModelEnum.unknow_table_mode,extend_keywords=extend_keywords)


class EndTableMode(BaseTableState):
    '''
    table 结束
    '''

    def __init__(self,extend_keywords=None):
        super(EndTableMode, self).__init__(mode=tableModelEnum.end_table_mode,extend_keywords=extend_keywords)


class TableExtractMachine(object):
    def __init__(self, extend_keywords=None, one_line_mode_return_result=True, text_replace_func=None):
        self.triedTree = get_keywordTredTree_instance()
        self.text_replace_func = text_replace_func
        # row 偏移
        self.row_shifting = 0

        if extend_keywords:
            self.triedTree = copy.deepcopy(self.triedTree)
            self.triedTree.update_triedTree_keys(extend_keywords)

        self.states = {
            "key_value_mode": KeyValueMode(extend_keywords=extend_keywords),
            "key_line_mode": KeyLineMode(extend_keywords=extend_keywords),
            'key_row_mode': KeyRowMode(extend_keywords=extend_keywords),
            "one_item_mode": OneItemMode(return_result=one_line_mode_return_result),
            "unknow_table_mode": UnknowTableMode(extend_keywords=extend_keywords),
            "end_table_mode": EndTableMode(extend_keywords=extend_keywords),
            "key_row_parsemode_mode": KeyRowParseModeMode(extend_keywords=extend_keywords),
        }
        self.current_state_str = "unknow_table_mode"
        self.current_state = self.states[self.current_state_str]

    def parsed_td_values_and_types(self, tr_node):
        return get_tr_parsed_value(tr_node, self.triedTree, self.text_replace_func)

    def reset(self):
        for key, status_mode in self.states.items():
            status_mode.reset_state()
        self.current_state_str = "unknow_table_mode"
        self.current_state = self.states[self.current_state_str]
        # row 偏移
        self.row_shifting = 0

    def update_parsemode_row_shifting(self, row_shifting):
        for key, status_mode in self.states.items():
            status_mode.row_shifting = row_shifting

    def run_table(self, table_node):
        tr_nodes = get_first_trs(table_node)
        # 检测每一列的前面的空格数量，并跟新到status里面去
        self.row_shifting = self.check_table_white_space(tr_nodes)
        self.update_parsemode_row_shifting(self.row_shifting)

        result = {}
        for index, tr in enumerate(tr_nodes):
            output = self.current_state.exec(self, tr)
            if output:
                result = combine_two_dict(result, output)

        output = self.current_state.output_result()
        if output != None and self.current_state_str != tableModelEnum.key_row_parsemode_mode:
            result = combine_two_dict(result, output)

        self.reset()
        return result

    def update_state(self, new_state, td_types):
        output = {}
        if self.current_state.check_if_need_reset(new_state, td_types):
            output = self.current_state.output_result()

            old_state_str = self.current_state_str
            self.current_state = self.states[new_state]
            self.current_state_str = new_state
            if old_state_str == tableModelEnum.key_row_parsemode_mode:
                if new_state == tableModelEnum.key_row_mode:
                    self.current_state.update_value_parse_mode(output)
                output = {}
            elif old_state_str == tableModelEnum.one_item_mode:
                if new_state == tableModelEnum.key_value_mode:
                    self.current_state.update_value_parse_mode(output)

        return output

    def check_list_null_num(self, list_str):
        count = 0
        for item in list_str:
            if not item:
                count += 1
            else:
                break
        return count

    def check_table_white_space(self, tr_nodes):
        tr_null_count = []
        for tr_node in tr_nodes:
            _, td_values = self.parsed_td_values_and_types(tr_node)
            tr_null_count.append(self.check_list_null_num(td_values))
        if tr_null_count:
            return min(tr_null_count)
        else:
            return 0


if __name__ == '__main__':
    from scrapy.selector import Selector
    from project_setting import PROJECT_ROOT
    import glob
    import os

    test_html_path = os.path.join(PROJECT_ROOT, "test/table_demo_html/*/*.html")
    for file in glob.glob(test_html_path):
        if "6.html" not in file or 'sgh' not in file:
            continue
        with open(file, encoding='utf-8') as fread:
            sel = Selector(text=fread.read())
        table_extractor = TableExtractMachine()
        tables = sel.xpath("//table")
        for table in tables:
            result = table_extractor.run_table(table)
            from pprint import pprint
            pprint(result)

